package com.xiaoyu.lanling.feature.moment.data

import `in`.srain.cube.request.FailData
import `in`.srain.cube.request.JsonData
import `in`.srain.cube.request.RequestHandler
import `in`.srain.cube.util.UriUtil
import `in`.srain.cube.util.internal.AppCallback
import android.net.Uri
import android.text.TextUtils
import com.xiaoyu.base.net.RequestCreator
import com.xiaoyu.base.utils.JsonUtil
import com.xiaoyu.base.utils.ListUtil
import com.xiaoyu.base.utils.MMKVUtil
import com.xiaoyu.base.utils.upload.UploadImageOriginalType
import com.xiaoyu.base.utils.upload.UploadListener
import com.xiaoyu.base.utils.upload.UploadMediaType
import com.xiaoyu.base.utils.upload.UploadParam
import com.xiaoyu.lanling.common.Constant
import com.xiaoyu.lanling.common.config.HttpConfig
import com.xiaoyu.lanling.event.moment.publish.PreviewListEvent
import com.xiaoyu.lanling.event.moment.publish.PublishEvent
import com.xiaoyu.lanling.event.moment.publish.ResultEvent
import com.xiaoyu.lanling.feature.moment.datamodel.FeedBelongType
import com.xiaoyu.lanling.feature.moment.datamodel.FeedType
import com.xiaoyu.lanling.feature.moment.datamodel.FeedType.IMAGE
import com.xiaoyu.lanling.feature.moment.datamodel.FeedType.TEXT
import com.xiaoyu.lanling.feature.moment.datamodel.FeedType.VIDEO
import com.xiaoyu.lanling.feature.moment.datamodel.PrivacyType
import com.xiaoyu.lanling.feature.moment.datamodel.PrivacyType.FeedPrivacyTypeDef
import com.xiaoyu.lanling.feature.moment.datamodel.publish.MediaAddItem
import com.xiaoyu.lanling.feature.moment.datamodel.publish.MediaImageItem
import com.xiaoyu.lanling.feature.moment.datamodel.publish.MediaItemBase
import com.xiaoyu.lanling.util.upload.UploadCategory
import org.json.JSONArray
import java.io.File
import java.util.*

class PublishData {
    @FeedBelongType.FeedBelongTypeDef
    private val feedBelongType = getFeedBelongType()

    private val mImageUriList: MutableList<Uri> = ArrayList()
    private val mTopicList: MutableList<String> = ArrayList()
    var text: String? = null
        private set
    private var mVideoUri: Uri? = null
    private var mAudioUri: Uri? = null
    private val mRemoteVideoPreviewImage: String? = null
    var mPrivacyType = PrivacyType.PUBLIC
        private set
    var mOriginal = false

    fun addText(draftText: String?) {
        text = draftText
    }

    fun addPicture(uriList: Collection<Uri>, original: Boolean) {
        mImageUriList.addAll(uriList)
        mOriginal = original
        require(mImageUriList.size <= MAX_COUNT_PICTURE) { "cannot exceed the limit" }
        notifyPreviewUpdate()
    }

    fun addAudio(uri: Uri) {
        mAudioUri = uri
        notifyPreviewUpdate()
    }

    fun addVideo(uri: Uri) {
        mVideoUri = uri
        notifyPreviewUpdate()
    }

    fun addPrivacyType(@FeedPrivacyTypeDef feedPrivacyType: String) {
        mPrivacyType = feedPrivacyType
    }

    fun notifyPreviewUpdate() {
        val previewList: MutableList<MediaItemBase> = ArrayList()
        var shouldDisplayAddLabel = true
        for (uri in mImageUriList) {
            previewList.add(MediaImageItem(previewList.size, uri.toString()))
        }
        if (previewList.size >= MAX_COUNT_PICTURE) {
            shouldDisplayAddLabel = false
        }
        mVideoUri?.let {
            if (previewList.isEmpty()) {
                if (UriUtil.isNetworkUri(it)) {
                    mRemoteVideoPreviewImage?.let { url ->
                        previewList.add(MediaImageItem(previewList.size, url))
                    }
                } else if (UriUtil.isLocalFileUri(it)) {
                    previewList.add(MediaImageItem(previewList.size, File(it.path).absolutePath))
                }
                shouldDisplayAddLabel = false
            }
        }
        if (shouldDisplayAddLabel) {
            previewList.add(MediaAddItem(previewList.size))
        }
        PreviewListEvent(previewList).post()
    }

    fun removePicture(uri: Uri?) {
        mImageUriList.remove(uri)
        notifyPreviewUpdate()
    }

    // TODO: 2018/4/25
    fun removePictureOrVideo(uri: Uri?) {
        if (mVideoUri != null) {
            mVideoUri = null
        } else {
            mImageUriList.remove(uri)
        }
        notifyPreviewUpdate()
    }

    fun removeVideo() {
        mVideoUri = null
        notifyPreviewUpdate()
    }

    fun removeAudio() {
        mAudioUri = null
        notifyPreviewUpdate()
    }

    fun removeTopic(tid: String?) {
        mTopicList.remove(tid)
    }

    fun hasTopic(tid: String?): Boolean {
        return mTopicList.contains(tid)
    }

    val isPublishInvalid: Boolean
        get() = TextUtils.isEmpty(text?.trim()) && !isPictureAdded && !isVideoAdded

    val isDraftEmpty: Boolean
        get() = isPublishInvalid && !isAudioAdded

    val isOnlyText: Boolean
        get() = !isPictureAdded && !isVideoAdded && !isAudioAdded

    val isPictureAdded: Boolean
        get() = !ListUtil.isNullOrEmpty(mImageUriList)

    val isVideoAdded: Boolean
        get() = mVideoUri != null

    val isAudioAdded: Boolean
        get() = mAudioUri != null

    val imageAddableCount: Int
        get() = MAX_COUNT_PICTURE - mImageUriList.size

    val audioUri: String
        get() = mAudioUri?.toString() ?: ""

    val videoUri: String
        get() = mVideoUri?.path ?: ""

    fun publish(text: String, @FeedPrivacyTypeDef privacyType: String) {
        val imageUriList: List<Uri> = mImageUriList
        val videoUri = mVideoUri
        val audioUri = mAudioUri
        @FeedType.FeedTypeDef val type = determineFeedType(text, imageUriList, videoUri)
        if (type == null) {
            notifyPublishResult(false)
            return
        }
        when (type) {
            TEXT -> publishTextType(text, audioUri, privacyType)
            IMAGE -> uploadImageAndPublish(imageUriList, text, audioUri, privacyType)
        }
        cleanDraft()
    }

    private fun uploadImageAndPublish(imageUriList: List<Uri>, text: String, audioUri: Uri?, @FeedPrivacyTypeDef privacyType: String) {
        val remotes: MutableList<String> = LinkedList()
        val locals: MutableList<String> = LinkedList()
        for (uri in imageUriList) {
            if (UriUtil.isNetworkUri(uri)) {
                remotes.add(uri.toString())
            } else if (UriUtil.isLocalFileUri(uri) && File(uri.path).exists()) {
                locals.add(File(uri.path).absolutePath)
            }
        }
        val originalType: Int = if (mOriginal) UploadImageOriginalType.ORIGINAL else UploadImageOriginalType.COMPRESSED
        UploadParam.batchUpload(locals, UploadCategory.FEED, UploadMediaType.IMAGE, "", originalType, object : AppCallback<List<String>> {
            override fun onSuccess(uploadSuccessUrls: List<String>) {
                val allRemoteUrls: MutableList<String> = ArrayList(remotes.size + uploadSuccessUrls.size)
                allRemoteUrls.addAll(remotes)
                allRemoteUrls.addAll(uploadSuccessUrls)
                publishImageType(allRemoteUrls, text, audioUri, privacyType)
            }

            override fun onError(e: Throwable) {
                notifyPublishResult(false)
            }
        })
    }

    @FeedType.FeedTypeDef
    private fun determineFeedType(text: String, pictureUriList: List<Uri>, videoUri: Uri?): String? {
        return if (!ListUtil.isNullOrEmpty(pictureUriList)) {
            IMAGE
        } else if (videoUri != null) {
            VIDEO
        } else if (!TextUtils.isEmpty(text)) {
            TEXT
        } else {
            null
        }
    }

    private fun publishImageType(remoteUrls: List<String>, text: String, audioUri: Uri?, @FeedPrivacyTypeDef privacyType: String) {
        val postData = HashMap<String, Any>()
        val payload = Payload.create().text(text).pictureList(remoteUrls)
        tryAppendAudio(audioUri, postData, payload, Runnable {
            postPublishRequest(IMAGE, postData, privacyType)
        })
    }

    private fun publishTextType(text: String, audioUri: Uri?, @FeedPrivacyTypeDef privacyType: String) {
        val postData = HashMap<String, Any>()
        val payload = Payload.create().text(text)
        tryAppendAudio(audioUri, postData, payload, Runnable {
            postPublishRequest(TEXT, postData, privacyType)
        })
    }

    private fun postPublishRequest(@FeedType.FeedTypeDef type: String, postData: Map<String, Any>, @FeedPrivacyTypeDef privacyType: String) {
        val requestCreator = RequestCreator.create(JsonData::class.java)
                .setRequestUrl(publishUrl)
                .addPostData("type", type)
                .addPostData("privacy", privacyType)
                .addPostData(postData)

        appendExtraPostData(requestCreator)
        requestCreator.setRequestHandler(object : RequestHandler<JsonData, JsonData> {
            override fun processOriginData(originData: JsonData): JsonData {
                return originData.optJson("data")
            }

            override fun onRequestFinish(jsonData: JsonData) {
                notifyPublishResult(true)
                PublishEvent(jsonData, feedBelongType).post()
            }

            override fun onRequestFail(failData: FailData) {
                notifyPublishResult(false)
            }
        })
        requestCreator.enqueue()
    }

    protected val publishUrl: String
        protected get() = HttpConfig.FEED_ADD_FEED

    protected fun appendExtraPostData(requestCreator: RequestCreator<JsonData>) {}
    protected fun appendExtraPostDataForVideo() {}

    fun getImagePosition(path: String?): Int {
        return mImageUriList.indexOf(Uri.parse(path))
    }

    val imagePreviewList: List<String>
        get() = ListUtil.convertToList(mImageUriList) { obj: Uri -> obj.toString() }

    class Payload {
        private val map: MutableMap<String, Any> = mutableMapOf()
        fun text(text: String): Payload {
            map["text"] = text
            return this
        }

        fun pictureList(remoteUrls: List<String>): Payload {
            map["images"] = remoteUrls.joinToString(",")
            return this
        }

        fun video(video: String): Payload {
            map["video"] = video
            return this
        }

        fun audio(audio: String): Payload {
            map["audio"] = audio
            return this
        }

        fun extraMap(extraMap: Map<String, Any>?): Payload {
            extraMap?.let {
                map.putAll(it)
            }
            return this
        }

        fun putTo(postData: HashMap<String, Any>) {
            postData.putAll(map)
        }

        companion object {
            fun create(): Payload {
                return Payload()
            }
        }
    }

    @FeedBelongType.FeedBelongTypeDef
    fun getFeedBelongType(): String {
        return FeedBelongType.MOMENT
    }

    fun saveDraft() {
        MMKVUtil.get()?.putString(Constant.MMKVKey.KEY_FEED_PUBLISH_DRAFT_PREFIX + feedBelongType, toJson().toString())
    }

    fun restoreDraft() {
        val jsonDataString = MMKVUtil.get()?.getString(Constant.MMKVKey.KEY_FEED_PUBLISH_DRAFT_PREFIX + feedBelongType, "")

        val jsonData: JsonData = JsonData.create(jsonDataString)
        val text: String = jsonData.optString("text")
        val videoUriString: String = jsonData.optString("videoUri")
        val audioUriString: String = jsonData.optString("audioUri")
        val feedPrivacyType: String = jsonData.optString("privacyType", PrivacyType.PUBLIC)
        val original: Boolean = jsonData.optBoolean("original", false)
        val pictureListData: JsonData = jsonData.optJson("imageUriList")

        this.text = text
        mVideoUri = if (TextUtils.isEmpty(videoUriString)) null else Uri.parse(videoUriString)
        mAudioUri = if (TextUtils.isEmpty(audioUriString)) null else Uri.parse(audioUriString)
        mPrivacyType = feedPrivacyType
        mImageUriList.addAll(JsonUtil.convertAsList(pictureListData) { url: Any -> Uri.parse(url.toString()) })
        mOriginal = original
        notifyPreviewUpdate()
    }

    fun cleanDraft() {
        MMKVUtil.get()?.putString(Constant.MMKVKey.KEY_FEED_PUBLISH_DRAFT_PREFIX + feedBelongType, "")
    }

    fun toJson(): JsonData {
        val jsonData: JsonData = JsonData.newMap()
        jsonData.put("text", text)
        jsonData.put("videoUri", mVideoUri)
        jsonData.put("audioUri", mAudioUri)
        jsonData.put("original", mOriginal)
        jsonData.put("imageUriList", JSONArray(ListUtil.convertToList(mImageUriList) { obj: Uri -> obj.toString() }))
        jsonData.put("privacyType", mPrivacyType)
        return jsonData
    }

    val topicList: List<String>
        get() = mTopicList

    val isEmptyLogiclly: Boolean
        get() = (TextUtils.isEmpty(text)
                && TextUtils.isEmpty(if (mVideoUri == null) "" else mVideoUri.toString())
                && TextUtils.isEmpty(if (mAudioUri == null) "" else mAudioUri.toString())
                && mImageUriList.size == 0 && TextUtils.equals(mPrivacyType, PrivacyType.PUBLIC))

    companion object {
        const val MAX_COUNT_PICTURE = 9
        const val MAX_COUNT_TOPIC = 3

        fun hasDraft(@FeedBelongType.FeedBelongTypeDef feedBelongType: String): Boolean {
            val draft = MMKVUtil.get()?.getString(Constant.MMKVKey.KEY_FEED_PUBLISH_DRAFT_PREFIX + feedBelongType, "")
            return !TextUtils.isEmpty(draft)
        }

        private fun tryAppendAudio(audioUri: Uri?, postData: HashMap<String, Any>, payload: Payload, publishRunnable: Runnable) {
            if (audioUri != null && UriUtil.isLocalFileUri(audioUri) && File(audioUri.path).exists()) {
                val audioFilePath = File(audioUri.path).absolutePath
                val uploadParam = UploadParam(audioFilePath, UploadCategory.FEED, UploadMediaType.AUDIO, "")
                payload.extraMap(uploadParam.extraMap)
                val uploadListener: UploadListener = object : UploadListener() {
                    override fun onSuccess(url: String) {
                        payload.audio(url)
                        payload.putTo(postData)
                        publishRunnable.run()
                    }

                    override fun onFailure(exception: Exception) {
                        notifyPublishResult(false)
                    }
                }
                uploadParam.upload(uploadListener)
                return
            }
            if (audioUri != null && UriUtil.isNetworkUri(audioUri)) {
                payload.audio(audioUri.toString())
            }
            payload.putTo(postData)
            publishRunnable.run()
        }

        private fun notifyPublishResult(success: Boolean) {
            if (success) {
                ResultEvent().post()
            } else {
                ResultEvent().fail().post()
            }
        }
    }
}